package de.rts.logic;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;

import de.rts.components.RailComponent;
import de.rts.components.Train;

public class TrainScheduler
{
  private PropertyChangeSupport                         changes                = new PropertyChangeSupport(this);
  private String                                        logText                = "";
  private static final String                           MESSAGE                = "flag";
  private ArrayBlockingQueue<String>                    firstSingleTrack       = new ArrayBlockingQueue<String>(1);
  private ArrayBlockingQueue<String>                    noSync2Track           = new ArrayBlockingQueue<String>(1);
  private ArrayBlockingQueue<String>                    secondSingleTrack      = new ArrayBlockingQueue<String>(1);
  private ArrayBlockingQueue<String>                    thirdSingleTrack       = new ArrayBlockingQueue<String>(1);
  private ArrayBlockingQueue<String>                    noSyncTrack            = new ArrayBlockingQueue<String>(1);
  private Queue<String>                                 switchUnit1SignalQueue = new LinkedList<String>();
  private Queue<String>                                 switchUnit2SignalQueue = new LinkedList<String>();
  private Queue<String>                                 switchUnit3SignalQueue = new LinkedList<String>();
  private Map<String, Integer>                          switchOccupationMap    = new HashMap<String, Integer>();
  private final Map<Integer, Queue<String>>             switchUnitSignalQueues = new HashMap<Integer, Queue<String>>()
                                                                               {
                                                                                 {
                                                                                   put(1, switchUnit1SignalQueue);
                                                                                   put(2, switchUnit2SignalQueue);
                                                                                   put(3, switchUnit3SignalQueue);
                                                                                 }
                                                                               };

  private final Map<String, ArrayBlockingQueue<String>> singleTracksMap        = new HashMap<String, ArrayBlockingQueue<String>>()
                                                                               {
                                                                                 {
                                                                                   put("seg 23910", firstSingleTrack);
                                                                                   put("seg 56910", firstSingleTrack);
                                                                                   put("seg 10111415", noSync2Track);
                                                                                   put("seg 10111516",
                                                                                     secondSingleTrack);
                                                                                   put("seg 12131516",
                                                                                     secondSingleTrack);
                                                                                   put("seg 15161617", thirdSingleTrack);
                                                                                   put("seg 15161718", noSyncTrack);
                                                                                 }
                                                                               };

  public TrainScheduler()
  {
  }

  public void enterSingleTrack(Train train, String segmentId)
  {
    ArrayBlockingQueue<String> queue = singleTracksMap.get(segmentId);
    if (queue == null) return;
    try
    {
      notifyLog(train.getName() + " wants to enter single track\n");
      if (queue.size() > 0) notifyLog(train.getName() + " has to wait for release\n");
      queue.put(MESSAGE);
    }
    catch (InterruptedException e)
    {
    }
    notifyLog(train.getName() + " now having exclusive lock on single track\n");
  }

  public void leaveSingleTrack(Train train, String segmentId)
  {
    ArrayBlockingQueue<String> queue = singleTracksMap.get(segmentId);
    if (queue == null)
    {
      System.err.println("Segment " + segmentId + " is not a single track segment");
      return;
    }
    try
    {
      notifyLog(train.getName() + " is leaving single track\n");
      String[] switches = RailComponent.ROUTESWITCHES.get(Integer.parseInt(train.getTrainId()));
      String nextSwitch = "";
      for (int i = 0; i < switches.length; i++)
      {
        if (i == switches.length - 1)
          nextSwitch = switches[i - 1];
        else if (((i + 1) < switches.length)) nextSwitch = switches[i + 1];
        if (nextSwitch.equals(segmentId))
        {
          queue = singleTracksMap.get(switches[i]);
          if (queue.size() == 0) return;
          queue.take();
          notifyLog(train.getName() + " is releasing lock\n");
          break;
        }
      }
    }
    catch (InterruptedException e)
    {
    }
  }

  public synchronized SwitchStatus requestSwitchAccess(Train train, int switchunitnumber)
  {
    Integer num = switchOccupationMap.get(train.getTrainId() + "_" + switchunitnumber);
    if (num != null && num.equals(switchunitnumber)) return SwitchStatus.OWNED;
    notifyLog(train.getName() + " requesting switch access\n");
    Queue<String> queue = switchUnitSignalQueues.get(switchunitnumber);
    if (queue.size() == 1 && !needsNoAccessPermission(train, switchunitnumber))
    {
      notifyLog(train.getName() + " access not granted " + "( occupied )\n");
      return SwitchStatus.OCCUPIED;
    }
    else if (queue.size() == 0 || needsNoAccessPermission(train, switchunitnumber))
    {
      switchOccupationMap.put(train.getTrainId() + "_" + switchunitnumber, switchunitnumber);
      queue.offer(MESSAGE);
      notifyLog(train.getName() + " reserved access\n");
      return SwitchStatus.RESERVED;
    }
    else
      return SwitchStatus.NO_SWITCH;
  }

  private boolean needsNoAccessPermission(Train train, int switchunitnumber)
  {
    Integer num = switchOccupationMap.get("3_2");
    Integer num2 = switchOccupationMap.get("1_2");
    boolean n1 = train.getTrainId().equals("1") && switchunitnumber == 2 && num != null;
    boolean n2 = train.getTrainId().equals("3") && switchunitnumber == 2 && num2 != null;
    return n1 || n2;
  }

  public synchronized SwitchStatus releaseSwitch(Train train, int switchunitnumber)
  {
    if (switchOccupationMap.get(train.getTrainId() + "_" + switchunitnumber) == null) return SwitchStatus.NOT_OWNED;
    Queue<String> queue = switchUnitSignalQueues.get(switchunitnumber);
    if (queue.size() == 0)
      return SwitchStatus.NO_SWITCH;
    else
    {
      queue.poll();
      notifyLog(train.getName() + " released switch\n");
      return SwitchStatus.RELEASED;
    }
  }

  public void notifyLog(String notification)
  {
    String lastNotification = this.logText;
    this.logText = notification;
    changes.firePropertyChange("logText", lastNotification, notification);
  }

  public void addPropertyChangeListener(PropertyChangeListener l)
  {
    changes.addPropertyChangeListener(l);
  }

  public void removePropertyChangeListener(PropertyChangeListener l)
  {
    changes.removePropertyChangeListener(l);
  }

  public void removeAllPropertyChangeListeners()
  {
    PropertyChangeListener[] listeners = changes.getPropertyChangeListeners();
    for (PropertyChangeListener l : listeners)
      changes.removePropertyChangeListener(l);
    {

    }
  }
}
